import { fabric } from 'fabric'
import { v4 as uuidv4 } from 'uuid'
import _ from 'lodash'
import { useOpAndViewStatus } from '@renderer/views/imgViewer/useOpAndViewStatus'

const idJoint = '_'
const controlBallRadius = 10


const currentMeasureType = 'line'
const measuringLineColor = 'green'
const measureDoneLineColor = 'yellow'
const { activeOpRef } = useOpAndViewStatus()

// 1. 在绘制中。那么只需要发送当前measuring 中的位置来获取 其值，得到值之后显示在measuring的位置。
// 2. 绘制完成，在操作中。后端返回所有

class CalcHelper {
  static getDistance([x, y]: [number, number], [x1, y1]: [number, number]): number {
    return Math.sqrt(Math.pow(x - x1, 2) + Math.pow(y - y1, 2))
  }
}

enum EnumMeasureStatus {
  create = 'create',
  canDone = 'canDone',
  selected = 'selected',
  selectedUpdating = 'selectedUpdating',
  done = 'done',
}

interface IMeasuring {
  id: string | number,
  type: string,
  status: EnumMeasureStatus,
  targetPointsCount: number,
  position: number[][],
  shapeObj: {
    controlIds: any[], // 控制点只在绘制中的状态下显示。
    lineIds: any[]
  }
}

interface IMeasureDone {
  id: string | number,
  type: string,
  status: EnumMeasureStatus,
  targetPointsCount: number,
  position: number[][],
  shapeObj: {
    controlIds: any[],
    lineIds: any[]
  }
}

const sendMsg = (msg: any) => {
  console.log(msg)
}


class Measure {
  // 记录已经完成的线段
  measureDoneMap: Map<string | number, IMeasureDone> = new Map<string, IMeasureDone>()
  // 记录正在绘制中的线段
  measuring: IMeasuring | null = null
  canvas: any
  // 记录点 => 线段
  point2LineMap: Map<string, string[]> = new Map()

  // 记录线段 => 点
  line2PointMap: Map<string, string[]> = new Map()
  // key 是每一个出现的shape的uniqueId, val 是形状
  shapeMap: Map<string, any> = new Map()


  constructor(canvas) {
    this.canvas = canvas
    this.point2LineMap = new Map<string, string[]>()
    this.line2PointMap = new Map<string, string[]>()
    this.shapeMap = new Map<string, any>()
  }

  createMeasuring(): IMeasuring {
    let count = 0
    switch (activeOpRef.value.type) {
      case 'line':
        count = 2
        break
      case 'angle':
        count = 3
        break
      default:
        break
    }
    return {
      id: uuidv4(),
      status: EnumMeasureStatus.create,
      position: [],
      targetPointsCount: count,
      type: currentMeasureType,
      shapeObj: {
        controlIds: [],
        lineIds: []
      }
    } as IMeasuring
  }

  checkMouseDownPoint(e) {
    let target: any = e.target
    if (target === null) {
      return false
    }
    const id = target.measureId
    this.moveMeasuring2Done()
    this.moveMeasureDone2SelectById(id)
    this.render()
    return true
  }

  clearMeasuring() {
    if (!this.measuring) {
      return
    }
    const measuring = structuredClone(this.measuring)
    measuring.shapeObj.controlIds.map((controlId: any) => {
      const control = this.shapeMap.get(controlId)
      this.canvas.remove(control)
      this.shapeMap.delete(controlId)
    })
    measuring.shapeObj.lineIds.forEach((lineId: any) => {
      const line = this.shapeMap.get(lineId)
      this.canvas.remove(line)
      this.shapeMap.delete(lineId)
    })
    this.measuring = null
  }

  updateMaps() {
    if (!this.measuring) {
      return
    }
    const lineIds = this.measuring.shapeObj.lineIds
    const controlIds = this.measuring.shapeObj.controlIds
    lineIds.forEach((lineId, index) => {
      this.line2PointMap.set(lineId, [controlIds[index], controlIds[index + 1]])
    })
    controlIds.forEach((controlId: any, index) => {
      if (index === 0) {
        this.point2LineMap.set(controlId, [lineIds[index]])
      } else if (index === controlIds.length - 1) {
        this.point2LineMap.set(controlId, [lineIds[index - 1]])
      } else {
        this.point2LineMap.set(controlId, [lineIds[index - 1], lineIds[index]])
      }
    })
  }

  moveMeasuring2Done() {
    if (!this.measuring) {
      return
    }
    const measuring = structuredClone(this.measuring)
    measuring.status = EnumMeasureStatus.done
    if (this.checkStatusCanDone()) {
      this.updateMaps()
    } else {
      this.clearMeasuring()
      return
    }
    this.measureDoneMap.set(measuring.id, measuring)
    measuring.shapeObj.controlIds.map((controlId: any) => {
      const control = this.shapeMap.get(controlId)
      control?.set({
        visible: false
      })
    })
    measuring.shapeObj.lineIds.forEach((lineId: any) => {
      const line = this.shapeMap.get(lineId)
      line.set({
        stroke: measureDoneLineColor
      })
    })
    this.measuring = null
  }

  moveMeasureDone2SelectById(id: number) {
    const measureDone = this.measureDoneMap.get(id)
    if (measureDone) {
      this.measuring = measureDone!
      this.measuring.status = EnumMeasureStatus.selected
      this.measuring.shapeObj.lineIds.forEach((lineShapeUniqueId: any) => {
        const line = this.shapeMap.get(lineShapeUniqueId)
        line?.set({
          stroke: measuringLineColor
        })
        line.sendToBack()
      })

      this.measuring.shapeObj.controlIds.map((controlShapeUniqueId: any) => {
        const control = this.shapeMap.get(controlShapeUniqueId)
        control?.set({
          visible: true,
        })
      })

    }
    this.measureDoneMap.delete(id)
  }

  render() {
    this.canvas.renderAll()
  }

  handleMouseDown(e, x, y) {
    if (this.measuring?.status !== EnumMeasureStatus.create && this.measuring?.status !== EnumMeasureStatus.canDone) {
      // 1. 如果没有正在绘制的线段。
      const hitExistPointFlag = this.checkMouseDownPoint(e)
      if (hitExistPointFlag) {
        console.log('命中了已有线段')
        return
      } else {
        if (this.measuring?.status === EnumMeasureStatus.selected) {
          this.moveMeasuring2Done()
          this.render()
          return
        }
      }
    }
    if (this.measuring === null) {
      this.measuring = this.createMeasuring()
    }
    let newPoints: number[][] = []
    if (this.measuring.position.length < this.measuring.targetPointsCount) {
      if (this.measuring.position.length === 0) {
        newPoints = [[x, y], [x, y]]
      } else {
        newPoints = [[x, y]]
      }
    }
    this.addNewShape(newPoints)
    this.measuring.position.push(...newPoints)
    this.render()
  }

  addNewShape(newPoints: number[][]) {
    if (!this.measuring) {
      return
    }
    let prePosition: number[] | null = null
    if (this.measuring.position.length > 0) {
      prePosition = this.measuring.position[this.measuring.position.length - 1]
    }
    let controls: any[] = []
    let lines: any[] = []
    let controlIds: any[] = []
    let lineIds: any[] = []
    const measuring = this.measuring!
    const currentControlIds = measuring.shapeObj.controlIds
    const currentLineIds = measuring.shapeObj.lineIds
    newPoints.forEach((oneMeasure, index) => {
      const [x, y] = oneMeasure
      const measureId = measuring.id
      const shapeId = `circle${idJoint}${index + currentControlIds.length}`
      const uniqueId = `${measureId}${idJoint}${shapeId}`
      const options: any = {
        left: x,
        top: y,
        strokeWidth: 2,
        radius: controlBallRadius,
        fill: '#fff',
        stroke: '#666',
        originX: 'center', // x轴方向以中心点为原点
        originY: 'center', // y轴方向以中心点为原点
        hasControls: false, // 不显示控制器
        hasBorders: false, // 不显示控制器的边
        lockRotation: true,
        measureId: measureId,
        shapeId: shapeId,
        uniqueId: uniqueId,
        selectable: true,
        evented: true    // 添加这个属性
      }
      const control = new fabric.Circle(options)
      this.shapeMap.set(uniqueId, control)
      controlIds.push(uniqueId)
      controls.push(control)
    })

    newPoints.forEach((_, index) => {
      let startPoint: any = null
      let endPoint: any = null
      if (prePosition && index === 0) {
        startPoint = prePosition
        endPoint = measuring.position[index]
      } else if (index > 0) {
        startPoint = newPoints[index - 1]
        endPoint = newPoints[index]
      }
      const measureId = measuring.id
      const shapeId = `line${idJoint}${currentLineIds.length + lineIds.length}`
      const uniqueId = `${measuring.id}${idJoint}${shapeId}`
      if (startPoint && endPoint) {
        const line = new fabric.Line([startPoint[0], startPoint[1], endPoint[0], endPoint[1]], {
          stroke: measuringLineColor,
          selectable: false,
          evented: true,    // 添加这个属性
          hasControls: false, // 不显示控制器
          hasBorders: false, // 不显示控制器的边
          perPixelTargetFind: true,
          targetFindTolerance: 100,  // 可以调整这个值来改变检测的精确
          // 自定义标识属性
          measureId: measureId,
          shapeId: shapeId,
          uniqueId: uniqueId,
          hoverCursor: 'default',
          strokeWidth: 4
        } as any)
        line.sendToBack()
        lineIds.push(uniqueId)
        lines.push(line)
        this.shapeMap.set(uniqueId, line)
      }
    })

    measuring.shapeObj.controlIds = [...measuring.shapeObj.controlIds, ...controlIds]
    measuring.shapeObj.lineIds = [...measuring.shapeObj.lineIds, ...lineIds]
    this.canvas.add(...lines)
    this.canvas.add(...controls)
  }


  handleMouseUp() {
    if (this.measuring === null) {
      return
    }
    if (this.measuring.status === EnumMeasureStatus.canDone && this.checkStatusCanDone()) {
      this.moveMeasuring2Done()
      this.render()
    }
  }

  handleMouseMove(x: number, y: number) {
    if (this.measuring === null) {
      return
    }
    if (![EnumMeasureStatus.create, EnumMeasureStatus.canDone].includes(this.measuring.status)) {
      return
    }
    const controlIds = this.measuring.shapeObj.controlIds
    const lineIds = this.measuring.shapeObj.lineIds
    this.measuring.position[this.measuring.position.length - 1] = [x, y]
    sendMsg({
      measuring: this.measuring.position,
    })
    const control = this.shapeMap.get(controlIds[controlIds.length - 1])
    const line = this.shapeMap.get(lineIds[lineIds.length - 1])
    control.set({
      left: x,
      top: y
    })
    line.set({
      x2: x,
      y2: y
    })

    if (this.checkStatusCanDone()) {
      this.measuring.status = EnumMeasureStatus.canDone
    }
    // 更新小球的坐标，以便其可以继续移动。
    control.setCoords()
    line.setCoords()
    this.render()
  }

  checkStatusCanDone() {
    if (this.measuring === null) {
      return
    }
    const position = this.measuring.position
    return this.measuring.position.length === this.measuring.targetPointsCount && CalcHelper.getDistance(position[position.length - 2] as any, position[position.length - 1] as any) >= 5
  }

  handleDragShape(uniqueId, x, y) {
    if (this.measuring === null) {
      return
    }
    if (this.measuring.status !== EnumMeasureStatus.selected) {
      return
    }
    const shapeIndex = Number.parseInt(uniqueId.split(idJoint)[2])

    const controlId = this.measuring.shapeObj.controlIds[shapeIndex]
    const control = this.shapeMap.get(controlId)
    control.set({
      left: x,
      top: y
    })
    this.measuring.position[shapeIndex] = [x, y]
    console.log(this.measuring.position)
    control.setCoords()
    const lineIds = this.point2LineMap.get(uniqueId)
    lineIds?.map(lineId => {
      const controlPointUniqueIds = this.line2PointMap.get(lineId)
      const index = controlPointUniqueIds!.indexOf(uniqueId)
      const line = this.shapeMap.get(lineId)
      if (index === 0) {
        line.set({
          x1: x,
          y1: y
        })
      } else if (index === 1) {
        line.set({
          x2: x,
          y2: y
        })
      }
      line.sendToBack()
      line.setCoords()
    })
    this.canvas.renderAll()
  }
}


export const useDrawMeasuring = (canvas) => {
  const measure = new Measure(canvas)
  canvas.on('mouse:down', (e: any) => {
    const { x, y } = e.pointer
    measure.handleMouseDown(e, Number.parseInt(x), Number.parseInt(y))
  })

  canvas.on('mouse:up', () => {
    measure.handleMouseUp()
  })

  canvas.on('mouse:move', function(e) {
    if (e.pointer) {
      const { x, y } = e.pointer
      measure.handleMouseMove(Number.parseInt(x), Number.parseInt(y))
    }
  })

  canvas.on('object:moving', _.throttle(function(e) {
    const { x, y } = e.pointer
    const target = e.target
    if (target.uniqueId) {
      const uniqueId = target.uniqueId
      measure.handleDragShape(uniqueId, Number.parseInt(x), Number.parseInt(y))
    }
  }))
  return measure
}
